
import { Component, v2, v3, Vec2, Vec3, _decorator } from 'cc';
import { Loger } from '../../loger/Loger';
import { AStarGrid, AStarGridType, AStarMapData } from './AStarData';

const { ccclass, menu, property } = _decorator;
@ccclass
@menu("AStar/AStarLogic")
/**A星寻路算法核心逻辑 */
export class AStarLogic extends Component {

    public gridsList: Array<Array<AStarGrid>>;

    /**地图数据 */
    public data: AStarMapData = null;



    init(data: AStarMapData) {
        this.data = data;

        //初始化地图数据
        this._resetGridsList();
    }

    private _resetGridsList() {
        this.gridsList = new Array(this.data.mapW);
        for (let col = 0; col < this.data.mapW; col++) {
            if (!this.gridsList[col]) {
                this.gridsList[col] = new Array(this.data.mapH);
            }
            for (let row = 0; row < this.data.mapH; row++) {
                this._addGrid(col, row, this.data.gridsType[col][row]);
            }
        }

    }

    private _addGrid(x: number, y: number, type: AStarGridType) {
        let grid: AStarGrid = {
            x: x,
            y: y,
            f: 0,
            g: 0,
            h: 0,
            type: type,
        }

        this.gridsList[x][y] = grid;
    }

    private _getGrid(x: number, y: number): AStarGrid {
        if (!this.gridsList[x] || !this.gridsList[x][y]) {
            return null;
        }
        return this.gridsList[x][y]
    }

    private _isEndGrid(grid: AStarGrid, endPos: Vec2): boolean {
        return grid.x == endPos.x && grid.y == endPos.y;
    }

    /**是否是直角拐点 */
    private _isRightAngle(curGrid: AStarGrid, beforeGrid: AStarGrid, nextGrid: AStarGrid): boolean {
        return (beforeGrid.x != nextGrid.x && beforeGrid.y != nextGrid.y) &&
            ((curGrid.x == beforeGrid.x && curGrid.y != beforeGrid.y) || (curGrid.y == beforeGrid.y && curGrid.x != beforeGrid.x)) &&
            ((curGrid.x == nextGrid.x && curGrid.y != nextGrid.y) || (curGrid.y == nextGrid.y && curGrid.x != nextGrid.x))
    }

    /**计算路径坐标 */
    public findPathPosList(startNodePos: Vec3, endNodePos: Vec3) {
        let startGridPos = this.getMapGridPos(startNodePos);
        let endGridPos = this.getMapGridPos(endNodePos);
        let path = this.findPath(startGridPos, endGridPos);
        if (!path) {
            return null;
        }

        let posList: Vec3[] = [];
        // let removeIdx: number = null;
        for (let i = 0; i < path.length; i++) {
            const one = path[i];
            // if (i > 0 && i < path.length - 1) {
            //     if (removeIdx != i - 1) {
            //         if (this._isRightAngle(one, path[i - 1], path[i + 1])) {
            //             removeIdx = i;
            //             continue;
            //         }
            //     }

            // }
            posList.push(this.getMapNodePos(one.x, one.y))
        }
        //第一个点改成当前位置
        posList[0] = startNodePos;
        //最后一个点改成目标位置
        posList[posList.length - 1] = endNodePos;
        return posList;
    }

    /**去除直角拐点,优化行进路线 */
    public findPathNoRightAngle(startGridPos: Vec2, endGridPos: Vec2): AStarGrid[] {
        let path = this.findPath(startGridPos, endGridPos);
        if (!path) {
            return null;
        }
        let newPath: AStarGrid[] = []
        let removeIdx: number = null;
        for (let i = 0; i < path.length; i++) {
            const one = path[i];
            if (i > 0 && i < path.length - 1) {
                if (removeIdx != i - 1) {
                    if (this._isRightAngle(one, path[i - 1], path[i + 1])) {
                        removeIdx = i;
                        continue;
                    }
                }

            }
            newPath.push(one);
        }

        return newPath;
    }


    public setGridType(x, y, type: AStarGridType) {
        this.gridsList[x][y].type = type;
        this.data.gridsType[x][y] = type;
    }

    public getGridType(x, y): AStarGridType {
        return this.gridsList[x][y].type;
    }

    public getMapGridPos(nodePos: Vec3) {
        let y = Math.max(Math.min(Math.floor(nodePos.y / this.data.gridH), this.data.mapH - 1), 0);
        let x = Math.max(Math.min(Math.floor(nodePos.x / this.data.gridW), this.data.mapW - 1), 0);
        return v2(x, y);
    }

    public getMapNodePos(gridX: number, gridY: number) {
        let x = gridX * this.data.gridW + this.data.gridW * 0.5;
        let y = gridY * this.data.gridH + this.data.gridH * 0.5;
        return v3(x, y);
    }

    /**查找路径 */
    public findPath(startGridPos: Vec2, endGridPos: Vec2): AStarGrid[] {
        this._resetGridsList();
        let startGrid = this._getGrid(startGridPos.x, startGridPos.y); //this.gridsList[startPos.x][startPos.y];
        let endGrid = this.gridsList[endGridPos.x][endGridPos.y];
        let openList: AStarGrid[] = [];
        let closeList: AStarGrid[] = [];
        let path: AStarGrid[] = [];
        openList.push(startGrid);
        let curGrid = openList[0];
        // while (openList.length > 0 && !this._isEndGrid(curGrid, endGridPos)) {
        while (openList.length > 0) {
            // 每次都取出f值最小的节点进行查找
            curGrid = openList[0];
            if (this._isEndGrid(curGrid, endGridPos)) {
                // Loger.log("find path success.");
                path.push(curGrid);
                while (curGrid.parent) {
                    curGrid = curGrid.parent;
                    path.push(curGrid);
                }
                path.reverse();
                this._resetGridsList();
                return path;
            }

            for (let i = -1; i <= 1; i++) {
                for (let j = -1; j <= 1; j++) {
                    if (i != 0 || j != 0) {
                        let col = curGrid.x + i;
                        let row = curGrid.y + j;
                        if (col >= 0 && row >= 0 && col < this.data.mapW && row < this.data.mapH
                            && this.gridsList[col][row].type != AStarGridType.Obstacles
                            && closeList.indexOf(this.gridsList[col][row]) < 0) {
                            if (this.data.is8dir) {
                                // 8方向 斜向走动时要考虑相邻的是不是障碍物
                                if (this.gridsList[col - i][row].type == AStarGridType.Obstacles || this.gridsList[col][row - j].type == AStarGridType.Obstacles) {
                                    continue;
                                }
                            } else {
                                // 四方形行走
                                if (Math.abs(i) == Math.abs(j)) {
                                    continue;
                                }
                            }

                            // 计算g值
                            let g = curGrid.g + Math.sqrt(Math.pow(i * 10, 2) + Math.pow(j * 10, 2));
                            if (this.gridsList[col][row].g == 0 || this.gridsList[col][row].g > g) {
                                this.gridsList[col][row].g = g;
                                // 更新父节点
                                this.gridsList[col][row].parent = curGrid;
                            }
                            // 计算h值 manhattan估算法
                            this.gridsList[col][row].h = Math.abs(endGridPos.x - col) + Math.abs(endGridPos.y - row);
                            // 更新f值
                            this.gridsList[col][row].f = this.gridsList[col][row].g + this.gridsList[col][row].h;
                            // 如果不在开放列表里则添加到开放列表里
                            if (openList.indexOf(this.gridsList[col][row]) < 0) {
                                openList.push(this.gridsList[col][row]);
                            }
                        }
                    }
                }
            }
            // 遍历完四周节点后把当前节点加入关闭列表
            closeList.push(curGrid);
            // 从开放列表把当前节点移除
            openList.splice(openList.indexOf(curGrid), 1);
            if (openList.length <= 0) {
                Loger.log("find path failed.");
                return null;
            }

            // 重新按照f值排序（升序排列)
            openList.sort((x: AStarGrid, y: AStarGrid) => {
                return x.f - y.f;
            });
        }
    }
}